package com.buou.mybatis_plus_generate.util;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.builder.Entity;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.LikeTable;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Property;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.cache.decorators.LoggingCache;

import java.awt.*;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 新版MyBatis-Plus生成代码模板工具类
 * MyBatis-Plus 3.5.1版本以上可以使用
 *
 * @author chai
 * @date 2023/3/15
 */
public class GenerateUtil {

    /**
     * 数据库地址
     */
    private static final String URL = "jdbc:mysql://localhost:3307/mybatis-plus-generate?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
    /**
     * 数据库用户名
     */
    private static final String USERNAME = "root";
    /**
     * 数据库密码
     */
    private static final String PASSWORD = "123456";

    public static void main(String[] args) {
        generate();
    }

    /**
     * 获取当前项目根目录
     *
     * @return 根目录
     */
    private static String getRootPath() {
        return System.getProperty("user.dir");
    }

    /**
     * 生成代码
     */
    private static void generate() {
        FastAutoGenerator.create(URL, USERNAME, PASSWORD)
                // 数据库配置
                .dataSourceConfig(getDataSourceConfigBuilder())
                // 全局配置
                .globalConfig(getGlobalConfigBuilder())
                // 包名配置
                .packageConfig(getPackageConfigBuilder())
                // 模板配置 (默认 Velocity, 需要在pom引入对应模板的依赖 可以在mybatis-plus-generator依赖中去复制)
                .templateConfig(getTemplateConfigBuilder())
                // 策略配置
                .strategyConfig(getStrategyConfigConfigBuilder())
                // 控制台输入式的策略配置
                // .strategyConfig(getScannerStrategyConfigConfigBuilder())
                // 自定义文件生成配置
                .injectionConfig(getInjectionConfigBuilder())
                // 使用vm模板生成文件
                .templateEngine(new VelocityTemplateEngine())
                .execute();
    }

    /**
     * 数据库配置
     */
    private static Consumer<DataSourceConfig.Builder> getDataSourceConfigBuilder() {
        return builder -> builder
                // 数据库表查询语句
                .dbQuery(new MySqlQuery())
                // 数据库字段类型转换
                .typeConvert(new MySqlTypeConvert())
                // 数据库关键词处理
                .keyWordsHandler(new MySqlKeyWordsHandler());
    }

    /**
     * 全局配置
     */
    private static Consumer<GlobalConfig.Builder> getGlobalConfigBuilder() {
        return builder -> builder
                // 作者信息
                .author("buou")
                // 文件输出目录
                .outputDir(getRootPath() + "/src/main/java")
                // 禁止打开输出目录
                .disableOpenDir()
                // 开始Spring文档模式 (与Swagger不可同时使用, Springdoc优先级更高)
                // .enableSpringdoc()
                // 开始Swagger模式
                .enableSwagger()
                // 关闭服务层接口
                // .disableServiceInterface()
                // 日期字段类型策略配置
                .dateType(DateType.TIME_PACK)
                // 注释的日志格式
                .commentDate(() -> DateFormatUtils.format(new Date(), "yyyy-MM-dd"));
    }

    /**
     * 包名配置
     */
    private static Consumer<PackageConfig.Builder> getPackageConfigBuilder() {
        return builder -> builder
                // 父包名
                .parent("com.buou")
                // 模块名
                .moduleName("mybatis_plus_generate")
                // Controller包名
                .controller("controller")
                // 服务接口包名
                .service("service")
                // 服务实现类包名
                .serviceImpl("service.impl")
                // mapper层包名
                .mapper("mapper")
                // xml包名 (在这里填写包名回使xml文件存放在/src/main/java/com/buou/mapper/xml下)
                // .xml("mapper.xml")
                // 实体类包名
                .entity("entity")
                // 路径配置信息 可以跳过parent配置(在这里配置xml文件输出路径可以放在resources目录下)
                .pathInfo(Collections.singletonMap(OutputFile.xml, getRootPath() + "/src/main/resources/mapper"));
    }

    /**
     * 模板配置 (默认 Velocity, 需要在pom引入对应模板的依赖 可以在mybatis-plus-generator依赖中去复制)
     */
    private static Consumer<TemplateConfig.Builder> getTemplateConfigBuilder() {
        return builder -> builder
                // Controller模板路径
                .controller("/templates/controller.java")
                // Service模板路径
                .service("/templates/service.java")
                // ServiceImpl模板路径
                .serviceImpl("/templates/serviceImpl.java")
                // Mapper模板路径
                .mapper("/templates/mapper.java")
                // Xml模板路径
                .xml("/templates/mapper.xml")
                // 实体模板路径
                .entity("/templates/entity.java")
                // kotlin实体模板路径
                .entityKt("/templates/entity.kt");
                // 禁用所有模板 此配置需要在最后执行才生效
                // .disable()
                // 禁用指定模板 此配置需要在最后执行才生效
                // .disable(TemplateType.ENTITY);
    }

    /**
     * 策略配置
     */
    private static Consumer<StrategyConfig.Builder> getStrategyConfigConfigBuilder() {
        return builder -> {
            builder
                    // 开启大写命名
                    .enableCapitalMode()
                    // 开启跳过视图
                    .enableSkipView()
                    // 禁用 sql 过滤 (禁用后Include和Exclude无法使用正则表达式匹配表列表)
                    .disableSqlFilter()
                    // 模糊表匹配 (只能配置一项) (表不是以t_开头的无法生成)
                    .likeTable(new LikeTable("t_", SqlLike.RIGHT))
                    // 模糊表排除
                    // .notLikeTable(new LikeTable("sys_", SqlLike.RIGHT))
                    // 增加表匹配 (只能配置一项) (要生成的表信息)
                    .addInclude("t_user_tmp")
                    // 增加表排除匹配
                    // .addExclude("t_code")
                    // 增加过滤表前缀 t_user -> user
                    .addTablePrefix("t_")
                    // 增加过滤表后缀 user_tmp -> user
                    .addTableSuffix("_tmp")
                    // 增加过滤字段前缀 is_delete -> delete
                    .addFieldPrefix("is_")
                    // 增加过滤字段后缀 create_time_gmt -> create_time
                    .addFieldSuffix("_gmt");
            // Controller策略配置
            builder.controllerBuilder()
                    // 覆盖已有文件
                    .enableFileOverride()
                    // 父类
                    // .superClass(BaseController.class)
                    // 开启驼峰转连字符
                    .enableHyphenStyle()
                    // 开启生成@RestController 控制器
                    .enableRestStyle()
                    // 格式化文件名称  (此两种方式取一即可)
                    // .formatFileName("%s" + ConstVal.CONTROLLER)
                    // 转换文件名称
                    .convertFileName(name -> name + ConstVal.CONTROLLER);
            // Service策略配置
            builder.serviceBuilder()
                    // 覆盖已有文件
                    .enableFileOverride()
                    // Service接口父类
                    // .superServiceClass(IService.class)
                    // ServiceImpl父类
                    // .superServiceImplClass(IServiceImpl.class)
                    .convertServiceFileName(name -> name + ConstVal.SERVICE)
                    .convertServiceImplFileName(name -> name + ConstVal.SERVICE_IMPL);
            // Mapper策略配置
            builder.mapperBuilder()
                    // 覆盖已有文件
                    .enableFileOverride()
                    // Mapper父类
                    .superClass(BaseMapper.class)
                    .convertMapperFileName(name -> name + ConstVal.MAPPER)
                    .convertXmlFileName(name -> name + ConstVal.MAPPER)
                    // 类上加入Mapper注解
                    .mapperAnnotation(Mapper.class)
                    // 开启BaseResultMap
                    .enableBaseResultMap()
                    // 开启BaseColumnList生成
                    .enableBaseColumnList()
                    // 设置缓存实现类
                    .cache(LoggingCache.class);
            // 实体类策略配置
            builder.entityBuilder()
                    // 覆盖已有文件
                    .enableFileOverride()
                    // 实体父类
                    // .superClass(BaseEntity.class)
                    // 名称转换实现类
                    // .nameConvert(new INameConvert.DefaultNameConvert(builder.build()))
                    // 转换文件名称
                    .convertFileName(name -> name)
                    // 禁止生成serialVersionUID
                    // .disableSerialVersionUID()
                    // 开启生成字段常量
                    // .enableColumnConstant()
                    // 开启链式模型
                    .enableChainModel()
                    // 开启lombok
                    .enableLombok()
                    // 开启 ActiveRecord 模型
                    .enableActiveRecord()
                    // 开启 Boolean 类型字段移除 is 前缀
                    // .enableRemoveIsPrefix()
                    // 开启生成实体时生成字段注解
                    .enableTableFieldAnnotation()
                    // 乐观锁字段名 (数据库) (配置一项即可)
                    .versionColumnName("")
                    // 乐观锁字段名 (实体)
                    // .versionPropertyName("")
                    // 逻辑删除字段名 (数据库) (配置一项即可)
                    // .logicDeleteColumnName("delete")
                    // 逻辑删除字段名 (实体)
                    .logicDeletePropertyName("delete")
                    // 数据库表映射到实体的命名策略 (下划线转驼峰)
                    .naming(NamingStrategy.underline_to_camel)
                    // 数据库表字段映射到实体的命名策略 (未指定按照 naming 执行)
                    .columnNaming(NamingStrategy.underline_to_camel)
                    // 添加父类字段 (superClass为空时不可用)
                    // .addSuperEntityColumns("page", "size")
                    // 添加实体生成时要忽略数据库的字段
                    .addIgnoreColumns("`ignore`")
                    // 添加表字段填充 (数据库字段方式)
                    // .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("update_time", FieldFill.INSERT_UPDATE))
                    // 实体类字段方式
                    .addTableFills(new Property("createTime", FieldFill.INSERT), new Property("updateTime", FieldFill.INSERT_UPDATE))
                    // 全局主键类型
                    .idType(IdType.AUTO);
        };
    }

    /**
     * 控制台输入式的策略配置
     */
    private static BiConsumer<Function<String, String>, StrategyConfig.Builder> getScannerStrategyConfigConfigBuilder() {
        return (scanner, builder) -> builder.addInclude(scanner.apply("请输入要生成信息的表名(全名), 多个表名用英文逗号分开: "));
    }

    /**
     * 注入配置 (自动生成其它类型的文件)
     */
    private static Consumer<InjectionConfig.Builder> getInjectionConfigBuilder() {
        Map<String, Object> customMap = new HashMap<>();
        // 处理package信息
        PackageConfig.Builder packageConfigBuilder = new PackageConfig.Builder();
        getPackageConfigBuilder().accept(packageConfigBuilder);
        PackageConfig packageConfig = packageConfigBuilder.build();
        Map<String, String> packageInfo = new HashMap<>(packageConfig.getPackageInfo());
        packageInfo.put("dto", packageConfig.getParent() + ".dto");
        customMap.put("package", packageInfo);
        return builder -> builder
                // 输出文件之前
                .beforeOutputFile((tableInfo, objectMap) -> {
                    System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
                    // 处理import信息
                    Set<String> importPackages = new TreeSet<>(tableInfo.getImportPackages());
                    importPackages.removeIf(pkg -> StringUtils.startsWith(pkg, "com.baomidou.mybatisplus.annotation"));
                    objectMap.put("dtoImportPackages", importPackages);
                })
                // 自定义参数 (可以传入到模板中)
                .customMap(customMap)
                .customFile(new CustomFile.Builder()
                        // 覆盖已有文件
                        .enableFileOverride()
                        // 文件名称 (类似TemplateConfig中的模板名称)
                        .fileName("DTO.java")
                        // 模板路径
                        .templatePath("/templates/dto.java.vm")
                        // 生成的包
                        .packageName("dto")
                        // 生成的文件的存放位置 (需填写全路径, 不填写则使用PackageConfig中的parent路径)
                        // .filePath("src/main/java/com/buou/mybatis_plus_generate")
                        .build());
    }

}
